package bamboo.swt;

import bamboo.util.History

import org.eclipse.swt._
import org.eclipse.swt.graphics.Color
import org.eclipse.swt.widgets._
import org.eclipse.swt.events._
import org.eclipse.swt.custom._
import org.eclipse.swt.layout._

class InterpreterView(parent: Composite) extends Composite(parent, SWT.NONE) {
  
  import scala.tools.nsc._
  import scala.tools.nsc.{InterpreterResults=>IR}
  import java.io.{ByteArrayOutputStream, StringWriter, PrintWriter}
  
  private val textWidget = new StyledText(this, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL)

  private val lineHistory = new History[String]("", 100)
  
  private val textWidgetWriter = new StringWriter() {
    override def flush() = {
      val buffer = this.getBuffer
      textWidget.append(buffer.toString)
      buffer.setLength(0)
    }
  }
  
  private val interpreter = new Interpreter(new Settings(), new PrintWriter(textWidgetWriter, true))
  
  setLayout(new FillLayout())
  textWidget.addVerifyKeyListener(new VerifyKeyListener() {
    def verifyKey(e: VerifyEvent) = e.keyCode match {
      case SWT.ARROW_UP => {
        triggerHistory(e, lineHistory.back)
      }
      case SWT.ARROW_DOWN => {
        triggerHistory(e, lineHistory.fwd)
      }
      case SWT.CR => {
        e.doit = false
        val code = if (textWidget.getSelectionCount > 0) textWidget.getSelectionText else getCurrentLine
        append("\n")
        lineHistory.add(code)
        interpret(code)
      }
      case _ => ()
    }
  })

  override def setFont(font: org.eclipse.swt.graphics.Font) = {
    textWidget.setFont(font)
  }
  
  trait InterpreterState extends (String => InterpreterState)
  object SingleLine extends InterpreterState {
    override def apply(code: String) = {
      interpreter.interpret(code) match {
        case IR.Incomplete => new InterpreterState {
          override def apply(more: String) = SingleLine(code + "\n" + more)
        } 
        case _ => SingleLine
      }
    }
  }
  
  var istate: InterpreterState = SingleLine
  
  def interpret(code: String)  {
    withColor(outputColor) {
      val output = capturingOut {
        istate = istate(code)
      }
      append(output)
    }
    withColor(interpreterOutputColor) {
      textWidgetWriter.flush()
    }
    moveCaret()
  }
  
  def capturingOut(block: => Unit) = {
    val output = new ByteArrayOutputStream()
    Console.withOut(output) {
      block
    }
    output.toString("utf-8")
  }
  
  private var scriptId = 0
  
  private def nextName = {
    val name = "script" + scriptId
    scriptId += 1
    name
  }
  
  def load(fname: String) {
    val source = scala.io.Source.fromFile(fname).mkString("")
    val objectName = nextName
    interpreter.compileString(
        "package interpreterview;\n " +
          "object " + objectName + " { " +
          source + 
          "}")
    val fullName = "interpreterview." + objectName
    import_(fullName + "._")
    interpret(fullName) // force object initialization
  }
  
  def import_(spec: String) {
    interpret("import " + spec)
  }
  
  private def withColor(color: Color) (block: => Unit) {
    val start = lastLineOffset
    try {
      block
    } finally {
      textWidget.setStyleRange(styleRange(start, lastLineOffset, color))
    }
  }
  
  val outputColor = getDisplay.getSystemColor(SWT.COLOR_DARK_GRAY)
  val interpreterOutputColor = getDisplay.getSystemColor(SWT.COLOR_GRAY)
  
  private def styleRange(begin: int, end: int, color: Color) = {
    val styleRange = new StyleRange()
    styleRange.start = begin
    styleRange.length = end-begin
    styleRange.foreground = color
    styleRange
  }

  def lastLineOffset = textWidget.getOffsetAtLine(textWidget.getLineCount()-1)
  
  def moveCaret() = {
    textWidget.setCaretOffset(lastLineOffset)
    textWidget.showSelection()
  }
  
  def getCurrentLine() = getLineAtOffset(textWidget.getCaretOffset)
  
  def getLineAtOffset(offset: int) = {
    val line = textWidget.getLineAtOffset(offset)
    textWidget.getContent.getLine(line)
  }

  def append(text: String) {
    textWidget.append(text)
  }

  private def triggerHistory(e: VerifyEvent, line: String) {
    moveCaret
    e.doit = false
    textWidget.replaceTextRange(lastLineOffset, getCurrentLine().length(), line)
  }  
}

